package runesim.node.model;
import java.util.*;
import org.apache.mina.common.IoSession;
import runesim.node.event.DelayedRunnable;
import runesim.node.net.ActionSender;
import runesim.node.util.*;
/**
 * Each connection or established connection is tied to one of these.
 * @author Jim
 */
public final class Player extends Mob {
	
			/**
			 * The user name of this player.
			 */
			String username;
			
			/**
			 * The last IP address of this user.
			 */
			String lastIP = "0.0.0.0";
			
			/**
			 * The current IP address of this user.
			 */
			String currentIP = "0.0.0.0";
			
			/**
			 * This player's appearance information.
			 */
			PlayerAppearance appearance;
			
			/**
			 * This player's inventory information.
			 */
			InventoryParent inventory = new InventoryParent(this);
			
			/**
			 * The current statistic numbers, (i.e. the number on top).
			 */
			int[] currentStats = new int[20];
			
			/**
			 * The max of the statistic numbers, (i.e. the number on bottom).
			 */
			int[] maxStats = new int[20];
			
			/**
			 * The XP of the statistics.
			 */
			double[] XP = new double[20];
			
			/**
			 * The privacy settings of this player.
			 * The public chat, private chat, and trade chat blocks.
			 */
			int[] privacySettings = new int[3];
			
			/**
			 * The equipment that the player is wearing on himself/herself.
			 */
			WornEquipmentParent equipment = new WornEquipmentParent(this);
			
			/**
			 * Whether or not this connection was re-established from an existing 
			 * session.
			 */
			boolean reconnecting = false;
			
			/**
			 * Whether or not this player has moved and changed the map area.
			 */
			boolean mapAreaChanged = true;
			
			/**
			 * Whether or not this player is currently running.
			 */
			boolean running = false;
			
			/**
			 * Whether or not this player is currently logged in.
			 */
			boolean loggedIn = false;
			
			/**
			 * The response code that will be sent to the client.
			 * @see runesim.shared.ResponseCodes
			 */
			byte responseCode;
			
			/**
			 * The player group that this player belongs to.
			 * Generally, 0 is a normal player, 1 is a moderator, and 2 is a JMOD member.
			 */
			int playerGroup = 0;
			
			/**
			 * The known region X coordinate currently.
			 */
			int knownRegionX;
			
			/**
			 * The known region Y coordinate currently.
			 */
			int knownRegionY;
			
			/**
			 * The hash of this player's name which is used for profile look up.
			 */
			long usernameHash;
			
			/**
			 * The IOSESSION that is basically the connection between here and the player.
			 */
			IoSession ioSession;
			
			/**
			 * The action sender class provides quick and easy methods for sending data to 
			 * the client without modifying state information.
			 */
			ActionSender actionSender = new ActionSender(this);
			
			/**
			 * A list of delayed events that will eventually happen.
			 */
			Map<String, DelayedRunnable> events = new HashMap<String, DelayedRunnable>();
			
			/**
			 * The number of quest points (QP) that this player has.
			 */
			int questPoints = 0;
			
			//update mask values
			/**
			 * The last chat message of this player used in chat mask updates.
			 */
			ChatMessage lastChatMessage = null;
			
			/**
			 * A list of players currently seen by this player and are therefore 
			 * sending their update masks to this player.
			 */
			StatefulEntityCollection<Player> watchedPlayers = new StatefulEntityCollection<Player>();
			
			/**
			 * A list of known appearance IDs for the watched players.
			 */
			Map<Player, Integer> knownAppearanceIds = new HashMap<Player, Integer>();
			
			/**
			 * This string is attached to an update mask, and it forces text over the 
			 * player's head. For example, toy horses.
			 */
			String textOverheadMessage;
			
			/**
			 * The EMOTEID to play in the update mask.
			 */
			int emoteIndex = -1;
			
			/**
			 * Delay of the EMOTEID playing (client cycles from now).
			 */
			int emoteDelay = 0;
			
			/**
			 * Constructor.
			 * @param ios		The IOSESSION to use with this player (cannot be reset).
			 */
			public Player(IoSession ios) {
				ioSession = ios;
			}
			
			/**
			 * Gets the delayed events list.
			 * @return		The delayed events list.
			 */
			public Map<String, DelayedRunnable> getEvents() {
				return events;
			}
			
			/**
			 * Gets this player's inventory.
			 * @return
			 */
			public InventoryParent getInventory(){
				return inventory;
			}
			
			/**
			 * Gets the action sender.
			 * @return		The action sender.
			 */
			public ActionSender getActionSender() {
				return actionSender;
			}
			
			/**
			 * Gets the over head text for update mask.
			 * @return		The string to send.
			 */
			public String getTextOverheadMessage(){
				return textOverheadMessage;
			}
			
			/**
			 * Gets the EMOTEID to send via update mask.
			 * @return		The EMOTEID to send.
			 */
			public int getEmoteIndex(){
				return emoteIndex;
			}
			
			/**
			 * Sets the EMOTEID to send via update mask.
			 * @param i		The EMOTEID to set.
			 */
			public void setEmoteIndex(int i){
				emoteIndex = i;
			}
			
			/**
			 * Sets the delay that the EMOTEID is played in client cycles from now.
			 * @param i		The delay to set.
			 */
			public void setEmoteDelay(int i){
				emoteDelay = i;
			}
			
			/**
			 * Gets the EMOTEID delay.
			 * @return		The delay of the current EMOTEID to play.
			 */
			public int getEmoteDelay(){
				return emoteDelay;
			}
			
			/**
			 * Resets the EMOTEID and EMOTEID delay fields.
			 */
			public void resetEmote(){
				emoteIndex = -1;
				emoteDelay = 0;
			}
			
			/**
			 * Sets the over head text message.
			 * @param s		The message to send in the update mask.
			 */
			public void setTextOverheadMessage(String s){
				textOverheadMessage = s;
			}
			
			/**
			 * Gets the last chat message that this player sent.
			 * @return		The chat message.
			 */
			public ChatMessage getLastChatMessage() {
				return lastChatMessage;
			}
			
			/**
			 * Sets the last chat message that this player sent.
			 * @param m		The chat message.
			 */
			public void setLastChatMessage(ChatMessage m) {
				lastChatMessage = m;
			}
			
			/**
			 * Gets the equipment that is currently worn by this player.
			 * @return		The equipment manager.
			 */
			public WornEquipmentParent getEquipment(){
				return equipment;
			}
			
			/**
			 * Sets the equipment that is currently worn by this player.
			 * @param equipment		The equipment instance to set.
			 */
			public void setEquipment(WornEquipmentParent equipment) {
				this.equipment = equipment;
			}
			
			/**
			 * Gets a specific privacy setting.
			 * @param index		The privacy setting to read.
			 * @return			The privacy setting read.
			 */
			public int getPrivacySetting(int index) {
				return privacySettings[index];
			}
			
			/**
			 * Sets a specific privacy setting.
			 * @param index		The privacy setting to set.
			 * @param value		The value to set it to.
			 */
			public void setPrivacySetting(int index, int value) {
				privacySettings[index] = value;
			}
			
			/**
			 * Gets all of the current statistics (the top number) in an array.
			 * @return		The current statistics.
			 */
			public int[] getCurrentStats() {
				return currentStats;
			}
			
			/**
			 * Gets a specific statistic from the specified skill ID.
			 * @param skill		The statistic to get from the skill ID.
			 * @return			The statistic lying at the skill ID.
			 */
			public int getCurrentStat(int skill) {
				return currentStats[skill];
			}
			
			/**
			 * Replaces the current statistics with a new array of values.
			 * @param replacingStatArray		The statistics to replace with.
			 */
			public void setCurrentStats(int[] replacingStatArray) {
				this.currentStats = replacingStatArray;
			}
			
			/**
			 * Sets a specific skill's statistic number.
			 * @param index		The skill to set.
			 * @param value		The value to set it to.
			 */
			public void setCurrentStat(int index, int value) {
				currentStats[index] = value;
			}
			
			/**
			 * Gets the maximum statistic values for each skill.
			 * @return		The max statistics.
			 */
			public int[] getMaxStats() {
				return maxStats;
			}
			
			/**
			 * Gets a specific maximum statistic for the specified skill.
			 * @param index		The skill to look up.
			 * @return			The max statistic of the specified skill.
			 */
			public int getMaxStat(int index) {
				return maxStats[index];
			}
			
			/**
			 * Replaces the maximum statistic array with a new one.
			 * @param maxStats		Array to replace with.
			 */
			public void setMaxStats(int[] maxStats) {
				this.maxStats = maxStats;
			}
			
			/**
			 * Sets a specific skill's maximum statistic value.
			 * @param index		The skill to set value to.
			 * @param value		The value to set to.
			 */
			public void setMaxStat(int index, int value) {
				maxStats[index] = value;
			}
			
			/**
			 * Gets all of the statistic XP values.
			 * @return		The statistic XP values.
			 */
			public double[] getXPs() {
				return XP;
			}
			
			/**
			 * Replaces current XP array with new one.
			 * @param exps		The array to replace with.
			 */
			public void setXPs(double[] exps) {
				this.XP = exps;
			}
			
			/**
			 * Adds XP to a specific skill.
			 * @param skill		The skill to add XP to.
			 * @param amount	The amount of XP to add.
			 */
			public void addToXP(int skill, double amount){
				XP[skill] += amount;
			}
			
			/**
			 * Gets the player's user name.
			 * @return		The user's user name.
			 */
			public String getUsername() {
				return username;
			}
			
			/**
			 * Gets the player's group.
			 * @return		The user's group.
			 */
			public int getPlayerGroup() {
				return playerGroup;
			}
			
			/**
			 * Gets the player's name in a 64bit long for storage look up.
			 * @return		The player's name in long form.
			 */
			public long getUsernameHash() {
				return usernameHash;
			}
			
			/**
			 * Whether or not the current player is logged in.
			 * @return		<code>true</code> if logged in, <code>false</code> otherwise.
			 */
			public boolean isLoggedIn() {
				return loggedIn;
			}
			
			/**
			 * Sets this user as logged in or not.
			 * @param loggedIn		The value to set to.
			 */
			public void setLoggedIn(boolean loggedIn) {
				this.loggedIn = loggedIn;
			}
			
			/**
			 * Gets this player's appearance.
			 * @return		The player's appearance.
			 */
			public PlayerAppearance getAppearance() {
				return appearance;
			}
			
			/**
			 * Replaces the current player's appearance with a new one.
			 * @param appearance		The appearance to set.
			 */
			public void setAppearance(PlayerAppearance appearance) {
				this.appearance = appearance;
			}
			
			/**
			 * Sets the login information of this player.
			 * @param u		The user name of this player.
			 * @param p		The password for this player.
			 * @param r		Whether or not this player is reconnecting.
			 */
			public void setCredentials(String u, String p, boolean r) {
				username = u;
				usernameHash = Data.playerNameToLong(u);
				reconnecting = r;
			}
			
			/**
			 * Gets this player's last IP address.
			 * @return		The last IP address of this player.
			 */
			public String getLastIP() {
				return lastIP;
			}
			
			/**
			 * Gets the IOSESSION tied to this user.
			 * @return		The IOSESSION used.
			 */
			public IoSession getIoSession() {
				return ioSession;
			}
			
			/**
			 * Sets this user's last IP address.
			 * @param lastIP		The IP address as a string to replace with.
			 */
			public void setLastIP(String lastIP) {
				this.lastIP = lastIP;
			}
			
			/**
			 * Gets the current IP address of this connected player.
			 * @return		The current IP address.
			 */
			public String getCurrentIP() {
				return currentIP;
			}
			
			/**
			 * Gets this player's quest points.
			 * @return		The player's quest point amount.
			 */
			public int getQP(){
				return questPoints;
			}
			
			/**
			 * Adds the specified amount of quest points to the player's total.
			 * @param amount	The amount of QP to add to the total.
			 */
			public void addToQP(int amount){
				questPoints += amount;
			}
			
			/**
			 * Sets the current IP address of this player.
			 * @param currentIP		The IP address to set the current IP address to.
			 */
			public void setCurrentIP(String currentIP) {
				this.currentIP = currentIP;
			}
			
			/**
			 * Gets the current response code that will be send.
			 * @return		The response code.
			 */
			public byte getResponseCode() {
				return responseCode;
			}
			
			/**
			 * Sets the response code.
			 * @param responseCode		The response code.
			 */
			public void setResponseCode(byte responseCode) {
				this.responseCode = responseCode;
			}
			
			/**
			 * Whether or not the map area is changed.
			 * @return		Whether or not the map area has been changed.
			 */
			public boolean isMapAreaChanged() {
				return mapAreaChanged;
			}
			
			/**
			 * Sets the map area changed or not.
			 * @param mapAreaChanged		The value to set.
			 */
			public void setMapAreaChanged(boolean mapAreaChanged) {
				this.mapAreaChanged = mapAreaChanged;
			}
			
			/**
			 * Whether or not this player is running.
			 * @return		Whether or not this player is running.
			 */
			public boolean isRunning() {
				return running;
			}
			
			/**
			 * Sets this player running or not.
			 * @param running		The value to set it to.
			 */
			public void setRunning(boolean running) {
				this.running = running;
			}
			
			/**
			 * Gets the list of watched players by this user.
			 * @return		The list of watched players.
			 */
			public StatefulEntityCollection<Player> getWatchedPlayers() {
				return watchedPlayers;
			}
			
			/**
			 * Sets the known region coordinates.
			 * @param x		The X coordinate
			 * @param y		The Y coordinate
			 */
			public void setKnownRegion(int x, int y) {
				knownRegionX = x;
				knownRegionY = y;
			}
			
			/**
			 * Gets the known region coordinates.
			 * @return		The known region coordinate (x, y).
			 */
			public int[] getKnownRegion() {
				return new int[] { knownRegionX, knownRegionY };
			}
			
			public void validatedWatchedPlayers() {
				for(Player p : watchedPlayers.getKnownEntities()) {
					if(!withinRange(p) || !p.isLoggedIn())
						removeWatchedPlayer(p);
				}
			}
			
			public void updateWatchedPlayers() {
				List<Player> playersInArea = getViewArea().getPlayersInView();
				for(Player p : playersInArea) {
					if(!p.equals(this)) {
						informOfPlayer(p);
						p.informOfPlayer(this);
					}
				}
			}
			
			private void informOfPlayer(Player p) {
				if((!watchedPlayers.contains(p) || watchedPlayers.isRemoving(p)) && !watchedPlayers.isAdding(p) && withinRange(p) && p.isLoggedIn()) {
					watchedPlayers.add(p);
					knownAppearanceIds.put(p, -1);
				}
			}
			
			public void updateAppearanceId() {
				if(getAppearance().hasChanged()) {
					for(Player p : watchedPlayers.getKnownEntities()) {
						p.knownAppearanceIds.put(this, getAppearance().getId());
					}
					knownAppearanceIds.put(this, getAppearance().getId());
					getAppearance().incrementId();
				}
			}
			
			public int getKnownAppearanceId(Player p) {
				return knownAppearanceIds.get(p);
			}
			
			public int setKnownAppearanceId(Player p, int i) {
				return knownAppearanceIds.put(p, i);
			}
			
			@Override
			public void setCombatLevel(int c) {
				super.setCombatLevel(c);
				getAppearance().setChanged(true);
			}
			
			@Override
			public void setLocation(Point p) {
				if(location != null) { // location could be null if we've just logged
					// in
					int[] knownMapArea = getKnownRegion();
					int[] newMapArea = { p.getRegionX(), p.getRegionY() };
					int[] differences = { Math.abs(newMapArea[0] - knownMapArea[0]), Math.abs(newMapArea[1] - knownMapArea[1]) };
					if(differences[0] >= 3 || differences[1] >= 3) {
						setMapAreaChanged(true);
					}
				} else {
					// if it is null we definitely need to send the map our packet
					setMapAreaChanged(true);
				}
				super.setLocation(p);
			}
			
			public void setPlayerGroup(int i) {
				if(i < 0 || i > 2)
					return;
				playerGroup = i;
			}
			
			@Override
			public String toString() {
				return "['" + username + "',idx=" + index + "]";
			}
			
			@Override
			public boolean equals(Object o) {
				if(o instanceof Player)
					return ((Player) o).getUsernameHash() == this.getUsernameHash();
				return false;
			}
			
			public boolean withinRange(Point p, int rad) {
				if(p == null)
					return false;
				int dX = Math.abs(location.getX() - p.getX());
				int dY = Math.abs(location.getY() - p.getY());
				return dX <= rad && dY <= rad;
			}
			
			public boolean withinRange(Entity e, int rad) {
				if(e == null)
					return false;
				return withinRange(e.getLocation(), rad);
			}
			
			public boolean withinRange(Entity e) {
				return withinRange(e, 16);
			}
			
			private void removeWatchedPlayer(Player p) {
				watchedPlayers.remove(p);
				knownAppearanceIds.remove(p);
			}
}